#!/usr/bin/env bash

############################################################################
# File Name: keepalived_install.sh
# Version: V1.0
# Author: Brian Hsiung
# Created Time : 2025-05-21 14:30:00
# Description: keepavlied安装部署脚本
###########################################################################

# keepalived版本，固定值
declare -r KEEPALIVED_VERSION="2.3.3"
# 主节点的IP
declare -r MASTER_IP="10.8.83.30"
# 从节点的IP
declare -r BACKUP_IP="10.8.83.31"
# VIP
declare -r KEEPALIVED_VIP="10.8.83.2"
# 网口名称，，主从一致
declare -r KEEPALIVED_INTERFACE="ens33"
# virtual_router_id 虚拟路由器ID，主从一致
declare -r VIRTUAL_ROUTER_ID="99"
# auth_pass 认证密码，，主从一致
declare -r AUTH_PASS="135790"

# ip link show | awk -F': ' '/state UP/ {print $2}'
# ip link show | awk -F': ' '/state UP/ {print $2; exit}'
# ip link show | awk -F': ' '/state UP/ && $2 !~ /veth|docker|br-/ {print $2; exit}'

# 阿里云访问地址
declare -r OSS_BASE_URL="https://brianhsiung.oss-cn-hangzhou.aliyuncs.com"

WORK_DIR="$(cd "$(dirname "$0")" && pwd)"
cd "$WORK_DIR" || exit

##############################################
# 日志函数
##############################################
log_info()    { echo -e "\033[34m[INFO] $*\033[0m"; }
log_warn()    { echo -e "\033[33m[WARNING] $*\033[0m"; }
log_success() { echo -e "\033[32m[SUCCESS] $*\033[0m"; }
log_error()   { echo -e "\033[31m[ERROR] $*\033[0m"; exit 1; }

##############################################
# 检查依赖
##############################################
check_dependency() {

    local -a required_cmds=("wget" "tar" "gcc" "make")
    local missing=()

    log_info "检查依赖命令: ${required_cmds[*]}"
    for cmd in "${required_cmds[@]}"; do
        if ! command -v "$cmd" &>/dev/null; then
            missing+=("$cmd")
        fi
    done
    if ! rpm -q openssl-devel >/dev/null 2>&1; then
        missing+=("openssl-devel")
    fi

    ((${#missing[@]})) && log_error "缺失依赖命令: ${missing[*]}"
}

##############################################
# 检查VIP是否可ping
##############################################
check_vip() {
    log_info "检查VIP: $KEEPALIVED_VIP"
    ping -c 5 -W 5 "$KEEPALIVED_VIP" &>/dev/null && log_error "VIP $KEEPALIVED_VIP 可ping通, 此IP已经被使用"
}

##############################################
# 编译安装keepalived
##############################################
install_keepalived() {
    log_info "编译安装keepalived"
    if [ ! -f keepalived-${KEEPALIVED_VERSION}.tar.gz ] && \
        curl --output /dev/null --silent --head --fail --connect-timeout 5 --max-time 10 "$OSS_BASE_URL"/health; then
        log_info "下载keepalived-${KEEPALIVED_VERSION}.tar.gz"
        wget -q "$OSS_BASE_URL/deploy/keepalived/keepalived-${KEEPALIVED_VERSION}.tar.gz" || log_error "下载keepalived-${KEEPALIVED_VERSION}.tar.gz失败"
    fi

    [ -f keepalived-${KEEPALIVED_VERSION}.tar.gz ] || log_error "keepalived-${KEEPALIVED_VERSION}.tar.gz不存在"

    tar -zxf keepalived-${KEEPALIVED_VERSION}.tar.gz
    cd keepalived-${KEEPALIVED_VERSION} || exit
    ./configure   # libnl3-devel libnl3-genl-devel 开启ipv6需要这两个包 ./configure
    make -j "$(nproc)" && make install
    keepalived --version || log_error "keepalived编译安装失败"

    if [ ! -f /usr/lib/systemd/system/keepalived.service ]; then
        log_info "添加keepalived.service"
        cat > /usr/lib/systemd/system/keepalived.service << EOF
[Unit]
Description=LVS and VRRP High Availability Monitor
After=network-online.target syslog.target
Wants=network-online.target
Documentation=man:keepalived(8)
Documentation=man:keepalived.conf(5)
Documentation=man:genhash(1)
Documentation=https://keepalived.org

[Service]
Type=forking
PIDFile=/run/keepalived.pid
KillMode=process
EnvironmentFile=-/usr/local/etc/sysconfig/keepalived
ExecStart=/usr/local/sbin/keepalived  \$KEEPALIVED_OPTIONS
ExecReload=/bin/kill -HUP \$MAINPID

LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
EOF
    fi
}
##############################################
# 添加keepalived配置文件
##############################################
config_keepalived() {

    if [ -f /etc/keepalived/keepalived.conf ]; then
        /bin/cp -f /etc/keepalived/keepalived.conf /etc/keepalived/keepalived.conf.bak."$(date +%Y%m%d%H%M%S)"
    fi
    [ -d /etc/keepalived ] || mkdir -p /etc/keepalived

    if [ "$(hostname -I | awk '{print $1}')" = "$MASTER_IP" ]; then
        log_info "当前主机为主节点，开始配置keepalived"
        cat > /etc/keepalived/keepalived.conf << EOF
! Configuration File for keepalived
global_defs {
   router_id LVS_DEVEL_M  # Master设置为 LVS_DEVEL_M Slave设置为LVS_DEVEL_S
   vrrp_skip_check_adv_addr
   vrrp_iptables
   vrrp_garp_interval 0
   vrrp_gna_interval 0
   
   script_user root
   enable_script_security
}

vrrp_script chk_haproxy {
    script "/etc/keepalived/chk_nginx.sh"
    interval 2
    weight -5
    fall 2  # 脚本连续几次都失败，则转换为失败
    rise 1  # 脚本连续几次都成功，把服务器从失败标记为成功
}

vrrp_instance VI_1 {
    state BACKUP             # 主备实例均设置为 BACKUP
    interface $KEEPALIVED_INTERFACE           # 设置网卡名，本示例配置为ens3 
    virtual_router_id $VIRTUAL_ROUTER_ID     # router_id (如果存在多套keepalived主从实例，则必须设置不同值)
    nopreempt                # 设置非抢占模式
    priority 100             # 设置优先级，数字越大，优先级越高，本示例配置主用实例优先级为100，从实例设置为50
    advert_int 1             # 设定MASTER与BACKUP主机之间同步检查的时间间隔（s）  
    authentication {
        auth_type PASS
        auth_pass $AUTH_PASS     # 设置密码 (如果存在多套keepalived主从实例，则必须设置不同值)
    }
    # 单播模式
    unicast_src_ip $MASTER_IP   # 设置当前实例的IP地址，本示例配置为10.8.83.30
    unicast_peer {
        $BACKUP_IP              # 对端实例的IP地址，本示例配置为10.8.83.31
    }
    virtual_ipaddress {
        $KEEPALIVED_VIP              # 设置HaVip的IP地址，本示例配置为10.8.83.2
    }
    
    # keepalived 依赖ARP报文更新IP信息，如果缺少以下参数，会导致某些场景下，主设备不发送 ARP 导致通信异常
    garp_master_delay 1   
    garp_master_refresh 5

    # 设置网络监控接口，任何一个接口出现故障，keepalived进入FAULT状态
    track_interface {
        $KEEPALIVED_INTERFACE                # 设置实例网卡名，本示例配置为ens3
    }

    # 检测脚本
    track_script {
        chk_haproxy
    }

}
EOF
    elif [ "$(hostname -I | awk '{print $1}')" = "$BACKUP_IP" ]; then
        log_info "当前主机为从节点，开始配置keepalived"
        cat > /etc/keepalived/keepalived.conf << EOF
! Configuration File for keepalived
global_defs {
   router_id LVS_DEVEL_S  # Master设置为 LVS_DEVEL_M Slave设置为LVS_DEVEL_S
   vrrp_skip_check_adv_addr
   vrrp_iptables
   vrrp_garp_interval 0
   vrrp_gna_interval 0
   
   script_user root
   enable_script_security
}

vrrp_script chk_haproxy {
    script "/etc/keepalived/chk_nginx.sh"
    interval 2
    weight -5
    fall 2  # 脚本连续几次都失败，则转换为失败
    rise 1  # 脚本连续几次都成功，把服务器从失败标记为成功
}

vrrp_instance VI_1 {
    state BACKUP             # 主备实例均设置为 BACKUP
    interface $KEEPALIVED_INTERFACE           # 设置网卡名，本示例配置为ens3 
    virtual_router_id $VIRTUAL_ROUTER_ID     # router_id
    nopreempt                # 设置非抢占模式
    priority 50              # 设置优先级，数字越大，优先级越高，本示例配置主用实例优先级为100，从实例设置为50
    advert_int 1             # 设定MASTER与BACKUP主机之间同步检查的时间间隔（s）  
    authentication {
        auth_type PASS
        auth_pass $AUTH_PASS     # 设置密码
    }
    # 单播模式
    unicast_src_ip $BACKUP_IP   # 设置当前实例的IP地址，本示例配置为10.8.83.31
    unicast_peer {
        $MASTER_IP              # 对端实例的IP地址，本示例配置为10.8.83.30
    }
    virtual_ipaddress {
        $KEEPALIVED_VIP               # 设置HaVip的IP地址，本示例配置为10.8.83.2
    }
    
    # keepalived 依赖ARP报文更新IP信息，如果缺少以下参数，会导致某些场景下，主设备不发送 ARP 导致通信异常
    garp_master_delay 1   
    garp_master_refresh 5

    # 设置网络监控接口，任何一个接口出现故障，keepalived进入FAULT状态
    track_interface {
        $KEEPALIVED_INTERFACE                # 设置实例网卡名，本示例配置为ens3
    }

    # 检测脚本
    track_script {
        chk_haproxy
    }

}
EOF
    else
        log_error "当前主机不是主节点或从节点，请检查脚本"
    fi
}

main() {
    [ "$EUID" -ne 0 ] && log_error "请使用root账号执行此脚本！"
    log_info "开始安装keepalived"
    check_dependency
    check_vip
    install_keepalived
    config_keepalived
    /bin/cp -f "$WORK_DIR/chk_nginx.sh" /etc/keepalived
    chmod 755 /etc/keepalived/chk_nginx.sh
    systemctl daemon-reload
    systemctl enable keepalived.service
    systemctl enable keepalived.service
    systemctl start keepalived.service
    systemctl status keepalived.service
    log_success  "keepalived安装完成"
}

main